﻿#pragma once

#include <chrono>
#include <iomanip>
#include <iostream>
#include <map>
#include <set>
#include <string_view>
#include <vector>

namespace xy
{

namespace utils
{

class scope_profiler
{
  public:
    using ClockType = std::chrono::high_resolution_clock;

    struct record
    {
        const char *tag;
        int us;
    };

  private:
    // 确保每个线程都有自己的 records
    inline thread_local static std::vector<record> records{};

    ClockType::time_point beg;
    ClockType::time_point end;
    const char *tag;

  private:
    scope_profiler(const char *tag, ClockType::time_point beg) : beg(beg), tag(tag)
    {
    }

    void on_destroy(ClockType::time_point end)
    {
        auto diff = end - beg;
        int us = std::chrono::duration_cast<std::chrono::microseconds>(diff).count();
        records.push_back({tag, us});
        int ms = std::chrono::duration_cast<std::chrono::milliseconds>(diff).count();
        std::cout << tag << " took " << ms << "ms" << std::endl;
    }

  public:
    scope_profiler(const char *tag) : scope_profiler(tag, ClockType::now())
    {
    }

    ~scope_profiler()
    {
        on_destroy(ClockType::now());
    }

    static void log(std::ostream &out = std::cout)
    {
        if (records.size() == 0)
            return;

        struct Statistic
        {
            int max_us = 0;
            int min_us = 0;
            int total_us = 0;
            int count_rec = 0;
            const char *tag = nullptr;
        };

        std::map<std::string_view, Statistic> stats;
        for (const auto &[tag, us] : records)
        {
            auto &stat = stats[tag];
            stat.total_us += us;
            stat.max_us = std::max(stat.max_us, us);
            stat.min_us = !stat.count_rec ? us : std::min(stat.min_us, us);
            stat.count_rec++;
            stat.tag = tag;
        }

        struct StatisticCompare
        {
            using value_type = std::pair<std::string_view, Statistic>;
            bool operator()(value_type const &lhs, value_type const &rhs) const
            {
                return lhs.second.total_us > rhs.second.total_us;
            }
        };

        std::multiset<std::pair<std::string_view, Statistic>, StatisticCompare> sortstats(stats.begin(), stats.end());
        auto dump = [&out](int val, int w) {
            auto tpwv = 1;
            for (int i = 0; i < w - 1; i++)
                tpwv *= 10;
            if (val > tpwv)
            {
                if (val / 1000 > tpwv / 10)
                    out << std::setw(w - 1) << val / 1000000 << 'M';
                else
                    out << std::setw(w - 1) << val / 1000 << 'k';
            }
            else
                out << std::setw(w) << val;
        };

        out << "   avg   |   min   |   max   |  total  | cnt | tag\n";
        for (auto const &[tag, stat] : sortstats)
        {
            dump(stat.total_us / stat.count_rec, 9);
            out << '|';
            dump(stat.min_us, 9);
            out << '|';
            dump(stat.max_us, 9);
            out << '|';
            dump(stat.total_us, 9);
            out << '|';
            dump(stat.count_rec, 5);
            out << '|';
            out << ' ' << tag << '\n';
        }
    }
};

} // namespace utils

} // namespace xy

#if defined(_MSC_VER)
#define ScopeProfiler xy::utils::scope_profiler _scopeProfiler(__FUNCSIG__);
#define ScopeProfilerNamed(tag) xy::utils::scope_profiler _scopeProfiler(__FUNCSIG__ #tag)
#elif defined(__GNUC__) || defined(__clang__)
#define ScopeProfiler xy::utils::scope_profiler _scopeProfiler(__PRETTY_FUNCTION__)
#define ScopeProfilerNamed(tag) xy::utils::scope_profiler _scopeProfiler(__PRETTY_FUNCTION__ #tag)
#else
#error "Unsupported compiler!"
#endif

#define ScopeProfilerLog xy::utils::scope_profiler::log()